#pragma once 

#include <hash_map>
#include <string>
#include <thread>
#include <mutex>

#include <boost/lexical_cast.hpp>

#include "../AbstractPeer.h"
#include "../PeerTestingFramework/TestingCommoands/TestingInstrcution.h"
#include "../../ObservableEvents/ObservableEventsList/ObservaleEventsList.h"
#include "../../UtilityClasses/HelperClass.h"

using std::string;
namespace Network
{
	namespace Peer
	{
		template<typename T, typename S>
		class P2pPeer : public AbstarctPeer<T,S>
		{
		public:
			using AbstarctPeer<T, S>::m_updatesList;
			using AbstarctPeer<T, S>::CloseConnection;
			using AbstarctPeer<T, S>::OpenConnection;
			using AbstarctPeer<T, S>::neighbors;
			using AbstarctPeer<T, S>::SendData;
			using AbstarctPeer<T, S>::m_updateThread;
			using AbstarctPeer<T, S>::BootUpSequence;
			using AbstarctPeer<T, S>::ListenOnNetwork;
			using AbstarctPeer<T, S>::bootStrapAddress;
			using AbstarctPeer<T, S>::logicalTimer;
			using AbstarctPeer<T, S>::UpdateFunction;
			using AbstarctPeer<T, S>::m_listeningThread;
			using AbstarctPeer<T, S>::m_canExit;
			using AbstarctPeer<T, S>::m_sockerNumber;
			using AbstarctPeer<T, S>::ReceiveData;
			using AbstarctPeer<T, S>::ipSet;

			VectorTimer* m_vectorTimer;
			bool notServer;

			P2pPeer() :AbstarctPeer()
			{
				notServer = true;
				m_sockerNumber = LISTENING_PORT;
				//BootUpSequence();
				ListenOnNetwork();
			}

			P2pPeer(string p_ipAddress, int p_socketNumber, int p_sequenceNumber, string p_nameAssigned, int p_amountPeers) : AbstarctPeer(p_ipAddress, p_socketNumber, p_nameAssigned)
			{
				notServer = false;
				m_vectorTimer = new FixedVectorTimer(p_sequenceNumber);
				m_sockerNumber = LISTENING_PORT + p_socketNumber;
				m_updatesList->SetVectorTimer(m_vectorTimer);
				this->m_updatesList = AbstarctPeer<T, S>::m_updatesList;
				BootUpSequence();
			}


			P2pPeer(string p_ipAddress, int p_socketNumber, int p_sequenceNumber, string p_nameAssigned, ObservableEvents::ObservableEventsList<T, S>* p_updateList, int p_amountPeers) : AbstarctPeer(p_ipAddress, p_socketNumber, p_nameAssigned, p_updateList)
			{
				notServer = false;
				m_vectorTimer = new FixedVectorTimer(p_sequenceNumber, p_amountPeers);
				m_updatesList->SetVectorTimer(m_vectorTimer);
				BootUpSequence();
			}

			P2pPeer(string p_ipAddress, int p_socketNumber, int p_sequenceNumber, string p_nameAssigned, ObservableEvents::ObservableEventsList<T, S>* p_updateList, int p_amountPeers, long p_fltSleepAfterComm, long p_fltSleepAfterFailedAttempt) : AbstarctPeer(p_ipAddress, p_socketNumber, p_nameAssigned, p_updateList)
			{
				notServer = false;
				m_fltSleepAfterComm = p_fltSleepAfterComm;
				m_fltSleepAfterFailedAttempt = p_fltSleepAfterFailedAttempt;
				m_vectorTimer = new FixedVectorTimer(p_sequenceNumber, p_amountPeers);
				m_updatesList->SetVectorTimer(m_vectorTimer);
				BootUpSequence();
			}


			virtual void BootUpSequence()
			{
				logicalTimer = 0;
				cout << bootStrapAddress << endl;
				m_updateThread = std::thread(&P2pPeer::ListenOnNetwork, this);
				m_senderThread = std::thread(&P2pPeer::SenderThread, this);
				//ListenOnNetwork();
			}

			virtual void ListenOnNetwork()
			{
				bool firstTimer = true;
				vector<unsigned char> receivedData;
				vector<unsigned char> replyingData;

				boost::asio::io_service aios;

				boost::asio::ip::tcp::acceptor acceptor(aios, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), m_sockerNumber));
				boost::asio::ip::tcp::socket socket(aios);
				
				if (notServer)
				{
					while (!m_canExit)
					{
						acceptor.accept(socket);

						std::string sClientIp = socket.remote_endpoint().address().to_string();
						
						string receivedDetails = ReceiveData(socket);
						
						neighbors->AddNeighbor(_strdup(sClientIp.c_str()), _strdup(receivedDetails.c_str()));
						string allKnownNeighbours = neighbors->GetRandomNeighbors() + "|";
						boost::asio::write(socket, boost::asio::buffer(allKnownNeighbours));
						CloseConnection(socket);
						//SendData(allKnownNeighbours, socket);
					}
				}
				else
				{
					while (!m_canExit)
					{
						try
						{
							acceptor.accept(socket);
						
							string currentPeerIp = socket.remote_endpoint().address().to_string();
							string currentPort = ReceiveData(socket);

							char* strIp = const_cast<char*>(currentPeerIp.c_str());
							char* strPort = const_cast<char*>(currentPort.c_str());

							neighbors->AddNeighbor(strIp, strPort);

							std::string localPeers = neighbors->GetRandomNeighbors();

							//send peers
							SendData(localPeers+"|", socket);
							string peers = ReceiveData(socket);

							//recieve peers 
							neighbors->AddNewNeighbours(peers);
							//first tick
							m_vectorTimer->IncrementTimer();

							//send available events
							m_updatesList->SerializeAllUuids(replyingData);
							cout << "Sending all data" << replyingData.size()<<  endl;
							SendData(replyingData, socket);

							replyingData.erase(replyingData.begin(), replyingData.end());
							receivedData.erase(receivedData.begin(), receivedData.end());

							//receive requested OE
							ReceiveData(socket, receivedData);
							m_vectorTimer->Serialize(replyingData);
							m_updatesList->GetNeededOE(receivedData, replyingData);
							cout << "Sending missing events" << endl;
							SendData(replyingData, socket);

							receivedData.erase(receivedData.begin(), receivedData.end());
							replyingData.erase(replyingData.begin(), replyingData.end());

							ReceiveData(socket, receivedData);
							m_updatesList->FindMissingOE(receivedData, replyingData);
							cout << "requesting missing events" << endl;
							SendData(replyingData, socket);

							receivedData.erase(receivedData.begin(), receivedData.end());

							ReceiveData(socket, receivedData);

							VectorTimer* temp = new FixedVectorTimer();
							temp->FromByteArray(receivedData, 0);
							//if global states not equal 
							std::lock_guard<std::mutex> myMutex(m_mutex);
							if (receivedData.size() > 20)
							{
								m_updatesList->MergeEvents(receivedData, (temp->GetAmountTimers() * 4 + 4));
							}

							m_vectorTimer->TakeSup(temp);

							m_vectorTimer->IncrementTimer();
							delete temp;

						}
						catch (exception e)
						{
							cout << "problem " << e.what()<< endl;
						}

						CloseConnection(socket);
						//neighbors->UpdateNeighborTime(currentPeerIp, currentPort);
					}
				}
			}

			virtual void SenderThread()
			{
				IoSrevice aios;
				Socket socket(aios);
				vector<unsigned char> receiveData;
				vector<unsigned char> replyingData;
				bool firstTimer = true;

				while (!m_canExit)
				{
					bool foundNewNeighbour = false;
					if (!notServer && firstTimer)
					{
						cout << "opening a new connection" << bootStrapAddress << "and" << LISTENING_PORT << endl;						
						OpenConnection(bootStrapAddress.c_str(), to_string(LISTENING_PORT).c_str(), socket, aios);
						string ipAddress = socket.local_endpoint().address().to_string();
						neighbors->SetInternalIp(ipAddress);
						m_ipAddress = ipAddress;
						ipSet = true;
						SendData(to_string(m_sockerNumber)+"|", socket);
						string data = ReceiveData(socket);
						CloseConnection(socket);
						firstTimer = false;
						cout << "adding new neighbours" << data << endl;
						neighbors->AddNewNeighbours(data);
					}

					Neighbour* details = nullptr;
					while (!foundNewNeighbour && !m_canExit)
					{
						if (m_canExit)
							break;
					
						details = nullptr;
						details = neighbors->GetRandomNeighbour();

						if (details != nullptr)
						{
							//will be opened form the thread sender
							foundNewNeighbour = OpenConnection(details->m_Ip, details->m_port, socket, aios);
						}

						if (!foundNewNeighbour)
						{
							this_thread::sleep_for(chrono::seconds(m_fltSleepAfterFailedAttempt));
						}
							
					}

					if (!foundNewNeighbour&&m_canExit)
					{
						break;
					}
						

					try
					{
						details->IncrementLogicalTime();
						SendData(to_string(m_sockerNumber) + "|", socket);
						//receive peers
						std::string receivedPeers = ReceiveData(socket);
						SendData(neighbors->GetRandomNeighbors() + "|", socket);
						neighbors->AddNewNeighbours(receivedPeers);
						//tick
						m_vectorTimer->IncrementTimer();
						ReceiveData(socket, receiveData);
						cout << receiveData.size() << endl;
						m_updatesList->FindMissingOE(receiveData, replyingData);
						cout << "sending required uuids" << replyingData.size() << endl;
						SendData(replyingData, socket);

						receiveData.erase(receiveData.begin(), receiveData.end());
						cout << "Receiving all data" << endl;
						ReceiveData(socket, receiveData);

						if (receiveData.size() > 20)
						{
							VectorTimer* temp = new FixedVectorTimer();
							temp->FromByteArray(receiveData, 0);
							//if global states not equal 
							std::lock_guard<std::mutex> myMutex(m_mutex);
							if (receiveData.size() > 0)
							{
								m_updatesList->MergeEvents(receiveData, (temp->GetAmountTimers() * 4 + 4));
							}

							m_vectorTimer->TakeSup(temp);
							m_vectorTimer->IncrementTimer();
							delete temp;
						}
						//send available events
						receiveData.erase(receiveData.begin(), receiveData.end());
						replyingData.erase(replyingData.begin(), replyingData.end());

						m_updatesList->SerializeAllUuids(replyingData);
						SendData(replyingData, socket);

						receiveData.erase(receiveData.begin(), receiveData.end());
						replyingData.erase(replyingData.begin(), replyingData.end());
						//receive requested OE
						// 
						ReceiveData(socket, receiveData);
						m_vectorTimer->Serialize(replyingData);
						m_updatesList->GetNeededOE(receiveData, replyingData);

						SendData(replyingData, socket);
					}
					catch (std::exception ex)
					{
						cout << "problem " << ex.what() << endl;
					}


					CloseConnection(socket);
					this_thread::sleep_for(chrono::seconds(m_fltSleepAfterComm));
				}
			}


			string GetMissingOE(string p_oeList)
			{
				vector<string> uuidList;
				HelperClass::ParseStringSentence(p_oeList, ";", uuidList);

				stringstream outputStream;

				for (int index = 0; index < uuidList.size(); index++)
				{
					if (!m_updatesList->find(boost::lexical_cast<uuid>(uuidList[index])))
					{
						outputStream << uuidList[index] << ";";
					}
				}

				return outputStream.str();
			}


			///-------------------------------------------------------------------------------------------------
			/// <summary>	Anti Entropy Starter. </summary>
			///
			/// <remarks>	Adrian, 21/09/2015. </remarks>
			///-------------------------------------------------------------------------------------------------

			virtual void UpdateFunction()
			{
			}

			using AbstarctPeer<T, S>::AddUpdate;
			virtual void AddUpdate(T* p_updateToAdd)
			{
				/*if (m_updatesList->AddLocalUpdate(p_updateToAdd))
				{
					UpdateFunction();
				}*/
			}

			~P2pPeer()
			{

			}

		private:
			Neighbour* m_chosenNeighbor;
			bool m_openedConnection = false;
			int m_attemptCounter = 0;
			bool m_currentlyExchanging = false;
			thread m_senderThread;
			std::mutex m_mutex;
			long m_fltSleepAfterComm = 0;
			long m_fltSleepAfterFailedAttempt = 0;



		};//class
	}//peer
}//network

